|
Beginner's Guide to WIMP Programming
Everything you need to know to start writing your own applications.
21: A Wimp Shell Program
Section 10 of this guide introduced a simple Wimp shell program: a collection of useful functions and procedures that could easily be adapted to form the basis of a new multi-tasking application with minimal effort. That first version of the shell program was based on what we had learnt to date in creating our !Test application, and itself formed the basis of our more sophisticated Shapes application.
Now that the Shapes application is complete, we can take some of the new functions and procedures that it contains and put them back into our shell in order to make it a more useful basis for future applications. Of course, as you become experienced in writing your own software, you will almost certainly wish to supplement the new shell with futher routines of your own.
If you need to refresh your memory about the shell program, reread Section 10; what follows here will supplement the simple shell program by extending the contents of its !RunImage file, but the other related files will not change.
Creating the Shell Program
If your !RunImage file is similar to the final version listed in the Appendix, you've already done most of the work as all you need to do is delete some of it, make a few changes and add a few procedures and functions (which are optional) on the end. First, save a copy of the final !RunImage from the Shapes application in the application directory for your new shell, so that you don't destroy your final copy of the Shapes program by accident!
You will need to go through your !RunImage file, comparing it with the listing at the end of this section, making appropriate deletions, additions and changes. You will find that there are differences in some of the function and procedure definitions, so check carefully. There are a few new (optional) ones at the end for you to enter, but any functions and procedures that are not listed as part of the shell should be deleted, as they were used by the Shapes application and are no longer needed.
You may wish to use your editor's Find/Replace facility to replace all instances of 'Shapes' with 'Shell', though note that the instance in PROCcreateicon (line 200) has a lower-case 's' as it is the name of a sprite.
Each function or procedure begins with a REM statement to remind you of what it does - you can, of course, leave them out if you wish.
PROCpoll contains a CASE ... OF structure to handle the reason codes you are almost certain to meet on return from Wimp_Poll, including reason code 1, a call to PROCredraw. If all your windows contain only icons, you can delete this line, together with PROCredraw itself.
We've removed the mask from R0 when calling Wimp_Poll, as you will need to create one to suit your own application. This was covered at the end of Section 17. Unless your program needs to respond to reason code zero (the null reason code), you should be sure to mask it out by putting a number in R0 with bit zero set (such as 1).
Error Handler Modifications
The error handler has been expanded to check for three error numbers. The number 1<<30 produces an error box with both 'OK' and 'Cancel' icons, (1<<30)+1 produces a box with just the 'OK' icon and (1<<30)+2 one with just the 'Cancel' icon. This last one enables you to produce a fatal error without having to show the line number in the error message.
PROCreceive now handles only the 'Close Down' message. You can add your own lines if you need to handle messages for loading and saving files.
PROCmouseclick has been reduced so that it caters only for Menu- and Select-clicks on the icon bar, and for clicks in the main window, which are handled by PROCwindow_click.
PROCget_origin works out the coordinates of a window's work area origin and returns them, thanks to the RETURN keywords before xorig% and yorig%.
PROCload_templates is only used to load and create the Info box and main window, but the lines that do this can be copied, with modifications, to load other windows.
Line 1330 puts the version string into the appropriate icon's indirected data. This line assumes that the 'version number' icon is icon number 4. You should check this in your template editor and either renumber the icons or alter the line if necessary. You should also check that the length of the icon's indirected data buffer is sufficient for your likely version number.
The menu creation routine, FNmake_menu, is basically as we wrote it earlier. We've included a couple of checks to ensure that the total size of our menus does not exceed the amount of memory we set aside for the purpose - we don't want to start overwriting other data!
PROCredraw has been included as it's a fairly standard routine for coping with requests from the Wimp to redraw a window. The procedure that it calls, though, PROCdraw, consists entirely of REM statements, because it has to be specially written for each individual application. Note that the window handle, in b%, is passed to PROCdraw; this is in case you have several windows that need redrawing, so that PROCdraw can distinguish between them.
More Useful PROCs and Functions
PROCterm simply puts a Return character - ASCII code 13 - on the end of any string starting at a%. This is necessary if the string has been produced by RISC OS, because it may end with a zero. If you want Basic to use the string, it has to end with a Return.
PROCdrag is included in case you want to drag an icon, for example at the start of a save operation. This procedure starts a drag of type 5, which means a box of rotating dashes. The initial position of the box is taken from the bounding box of the icon where you held down the mouse button.
FNicon_state checks whether or not a particular icon is selected, and returns TRUE if it is. This function makes dialogue boxes particularly easy to use. You don't have to worry about icons being selected or deselected, or 'radio' type icons cancelling each other through their exclusive selection group - the Wimp takes care of all that. You simply have to check the state of the icons when they have some relevance to what your program does.
PROCforce_redraw takes the screen coordinates of the visible part of a window and forces the Wimp to redraw that rectangle. This is a simple way of making any changes to a window instantly visible.
FNwind_open simply returns TRUE if a window is open.
PROCpush simulates clicking the mouse over a particular icon. You could use this, for example, to make the default action button of a dialogue box appear to slab in when you press Return, just before the box vanishes from the screen. Note, though, that this routine is of most interest for programs that run on older hardware, because on fast machines the window is likely to close so quickly that you won't have time to see the slabbing-in effect. As explained in Section 20, considering the fact that this procedure uses a discouraged technique to produce its effect (albeit the only way of achieving it), you may prefer not to make use of PROCpush in your own programs.
Additional Routines
We've included several short routines that were not in our example programs, but which can be useful when handling icons and menus.
FNtick checks whether or not a menu item is ticked, and returns TRUE if it is; FNtoggle_tick changes the ticked or unticked state of an item; PROCgrey disables a menu item by turning it grey and PROCungrey reverses the process. These procedures work by altering the appropriate bits in the menu item flags.
FNstring_addr returns the address of an indirected icon's text string; FNicon_string uses this to return the actual string and PROCset_icon_string puts a string into the icon's text buffer. Note that these routines do not check to ensure that the icon does have indirected text - it's up to you to ensure that it does when you design your window. Note also that PROCset_icon_string doesn't check the size of the icon's buffer before putting a string into it. Again, it's up to you to ensure that enough space is available (or alter the routines to make them more foolproof).
FNindirect puts a string into the indirected data workspace and returns its address; very useful when creating icons with indirected data.
No doubt you can think of many other useful routines to include in your shell.
When you write your own application, you would probably do best to keep all these routines intact at first, until you have finally finished and debugged your program. Then you can delete any that you haven't used, and reduce the sizes of the data blocks at b%, ws% and menspc% to whatever your program needs.
It is now up to you and your inventiveness to see what you can get out of Wimp programming. You will find that there is something very satisfying about producing an application that has the same 'feel' as expensive, commercially produced software.
And have fun!
10 REM >!RunImage
20 REM (C) Martyn Fox
30 REM Wimp shell program
40 version$="0.01 (date)"
50 ON ERROR PROCclose:REPORT:PRINT" at line ";ERL:END
60 SYS "Wimp_Initialise",200,&4B534154,"Shell" TO ,task%
70 PROCinit
80 PROCcreateicon
90 ON ERROR IF FNerror THEN PROCclose:END
100 REPEAT
110 PROCpoll
120 UNTIL quit%
130 PROCclose
140 END
150 :
160 DEFPROCcreateicon
170 REM creates the application's icon and puts it on the icon bar
180 REM b%+0 = -1 for right side and -2 for left side
190 !b%=-1:b%!4=0:b%!8=0:b%!12=68:b%!16=68:b%!20=&3002
200 $(b%+24)="!shell":SYS"Wimp_CreateIcon",,b% TO i%
210 ENDPROC
220 :
230 DEFPROCclose
240 REM tells the Wimp to quit the application
250 SYS "Wimp_CloseDown",task%,&4B534154
260 ENDPROC
270 :
280 DEFPROCpoll
290 REM main program Wimp polling loop
300 SYS "Wimp_Poll",,b% TO r%
310 CASE r% OF
320 WHEN 1:PROCredraw
330 WHEN 2:SYS "Wimp_OpenWindow",,b%
340 WHEN 3:SYS "Wimp_CloseWindow",,b%
350 WHEN 6:PROCmouseclick
360 WHEN 8:PROCkeypress
370 WHEN 9:PROCmenuclick
380 WHEN 17,18:PROCreceive
390 ENDCASE
400 ENDPROC
410 :
420 DEFPROCmouseclick
430 REM handles mouse clicks in response to Wimp_Poll reason code 6
440 REM b%!0=mousex,b%!4=mousey:b%!8=buttons:b%!12=window handle (-2 for icon bar):b%!16=icon handle
450 CASE b%!12 OF
460 WHEN -2:
470 CASE b%!8 OF
480 WHEN 2:PROCshowmenu(mainmenu%,!b%-64,96+2*44):REM replace '2' with number of main menu items
490 WHEN 4:!b%=main%:SYS "Wimp_GetWindowState",,b%:SYS "Wimp_OpenWindow",,b%
500 ENDCASE
510 WHEN main%:PROCwindow_click
520 ENDCASE
530 ENDPROC
540 :
550 DEFPROCget_origin(handle%,RETURN xorig%,RETURN yorig%)
560 REM returns coordinates of window work area origin
570 LOCAL c%
580 c%=FNstack(36)
590 !c%=handle%
600 SYS "Wimp_GetWindowState",,c%
610 xorig%=c%!4-c%!20:yorig%=c%!16-c%!24
620 PROCunstack(c%)
630 ENDPROC
640 :
650 DEFFNstack(size%)
660 REM allocates temporary memory from stack block
670 REM stack must be cleared after use with PROCunstack
680 IF stackptr%+size%>stackend% ERROR 1,"No room in stack"
690 stackptr%+=size%
700 =stackptr%-size%
710 :
720 DEFPROCunstack(old_ptr%)
730 REM removes temporary memory from stack
740 stackptr%=old_ptr%
750 IF stackptr%<stack% stackptr%=stack%
760 ENDPROC
770 :
780 DEFFNmake_menu
790 REM creates menu block from DATA statements
800 LOCAL start%,title$,item$,ul%,tail$,writable%,buffer%,buflen%
810 IF menspc%+28>menend% ERROR (1<<30)+2,"Not enough menu space"
820 start%=menspc%
830 READ title$
840 $(start%)=title$
850 start%?12=7:REM title foreground colour
860 start%?13=2:REM title background colour
870 start%?14=7:REM work area foreground colour
880 start%?15=0:REM work area background colour
890 start%!20=44:REM height of menu items
900 start%!24=0:REM gap between items
910 width%=LEN(title$)-3
920 menspc%+=28
930 REPEAT
940 READ item$
950 IF item$<>"*" THEN
960 IF menspc%+24>menend% ERROR (1<<30)+2,"Not enough menu space"
970 !menspc%=0
980 writable%=FALSE
990 ul%=INSTR(item$,"_")
1000 IF ul% THEN
1010 tail$=RIGHT$(item$,LEN(item$)-ul%)
1020 IF INSTR(tail$,"T") !menspc%=!menspc% OR 1:REM tick
1030 IF INSTR(tail$,"D") !menspc%=!menspc% OR 2:REM dotted line
1040 IF INSTR(tail$,"W") !menspc%=!menspc% OR 4:writable%=TRUE:READ buffer%:READ buflen%:REM writable icon
1050 IF INSTR(tail$,"M") !menspc%=!menspc% OR 8:REM generate message
1060 item$=LEFT$(item$,ul%-1)
1070 ENDIF
1080 IF LENitem$>width% width%=LENitem$
1090 menspc%!4=-1:REM submenu ptr
1100 IF writable% THEN
1110 menspc%!8=&0700F121:menspc%!12=buffer%:menspc%!16=-1:menspc%!20=buflen%:$buffer%=item$
1120 ELSE
1130 IF LENitem$<12 THEN
1140 menspc%!8=&07000021:$(menspc%+12)=item$
1150 ELSE
1160 menspc%!8=&07000121:menspc%!12=ws%:menspc%!16=-1:menspc%!20=LENitem$+1
1170 $ws%=item$:ws%+=LENitem$+1
1180 ENDIF
1190 ENDIF
1200 menspc%+=24
1210 ENDIF
1220 UNTIL item$="*"
1230 start%!16=width%*16+32
1240 !(menspc%-24)=!(menspc%-24) OR &80
1250 mptr%=menspc%
1260 =start%
1270 :
1280 DEFPROCload_templates
1290 REM opens window template file, loads and creates window
1300 SYS "Wimp_OpenTemplate",,"<Shell$Dir>.Templates"
1310 REM ****** load and create Info box ******
1320 SYS "Wimp_LoadTemplate",,stack%,ws%,wsend%,-1,"progInfo",0 TO ,,ws%
1330 $stack%!(88+32*0+20)=version$
1340 SYS "Wimp_CreateWindow",,stack% TO info%
1350 REM ****** load and create main window ******
1360 SYS "Wimp_LoadTemplate",,stack%,ws%,wsend%,-1,"Main",0 TO ,,ws%
1370 SYS "Wimp_CreateWindow",,stack% TO main%
1380 REM ****** end of window creation ******
1390 SYS "Wimp_CloseTemplate"
1400 ENDPROC
1410 :
1420 DEFPROCattach(menu%,item%,sub%)
1430 REM attach submenu or dialogue box to main menu
1440 !(menu%+28+item%*24+4)=sub%
1450 ENDPROC
1460 :
1470 DEFPROCinit
1480 REM initialisation before polling loop starts
1490 DIM b% 255,ws% 1023,menspc% 1023,stack% 1023
1500 wsend%=ws%+1024:menend%=menspc%+1024:stackend%=stack%+1024:stackptr%=stack%
1510 quit%=FALSE
1520 PROCload_templates
1530 PROCmenus
1540 ENDPROC
1550 :
1560 DEFPROCreceive
1570 REM handles messages received from the Wimp with reason codes 17 or 18
1580 CASE b%!16 OF
1590 WHEN 0:quit%=TRUE
1600 ENDCASE
1610 ENDPROC
1620 :
1630 DEFPROCkeypress
1640 REM processes keypresses in response to Wimp_Poll reason code 8
1650 LOCAL key%
1660 key%=b%!24
1670 CASE key% OF
1680 OTHERWISE
1690 SYS "Wimp_ProcessKey",key%
1700 ENDCASE
1710 ENDPROC
1720 :
1730 DEFPROCwindow_click
1740 REM handles mouse clicks on window
1750 REM b%!0=mousex,b%!4=mousey:b%!8=buttons:b%!12=window handle (-2 for icon bar):b%!16=icon handle
1760 VDU 7
1770 ENDPROC
1780 :
1790 DEFPROCmenus
1800 REM create menus and attach submenus and dialogue boxes
1810 PROCmain_menu
1820 PROCattach(mainmenu%,0,info%)
1830 ENDPROC
1840 :
1850 DEFPROCshowmenu(menu%,x%,y%)
1860 REM opens menu at given coordinates
1870 topmenu%=menu%:topx%=x%:topy%=y%
1880 SYS "Wimp_CreateMenu",,menu%,x%,y%
1890 ENDPROC
1900 :
1910 DEFPROCmenuclick
1920 REM handles mouse clicks on menu in response to Wimp_Poll reason code 9
1930 LOCAL c%,adj%
1940 c%=FNstack(20)
1950 SYS "Wimp_GetPointerInfo",,c%
1960 adj%=(c%!8 AND 1)
1970 SYS "Wimp_DecodeMenu",,topmenu%,b%,c%
1980 CASE $c% OF
1990 WHEN "Quit":quit%=TRUE
2000 ENDCASE
2010 IF adj% PROCshowmenu(topmenu%,topx%,topy%)
2020 PROCunstack(c%)
2030 ENDPROC
2040 :
2050 DEFPROCmain_menu
2060 REM creates main menu, calling FNmake_menu
2070 RESTORE +1
2080 DATA Shell,Info,Quit,*
2090 mainmenu%=FNmake_menu
2100 ENDPROC
2110 :
2120 DEFPROCredraw(b%)
2130 REM redraws window contents
2140 LOCAL xorig%,yorig%,more%
2150 PROCget_origin(!b%,xorig%,yorig%)
2160 SYS "Wimp_RedrawWindow",,b% TO more%
2170 WHILE more%
2180 PROCdraw(b%,xorig%,yorig%)
2190 SYS "Wimp_GetRectangle",,b% TO more%
2200 ENDWHILE
2210 ENDPROC
2220 :
2230 DEFPROCdraw(b%,xorig%,yorig%)
2240 REM called when all or part of window needs redrawing
2250 REM xorig% and yorig% are coordinates of work area origin (top left-hand corner of window work area)
2260 REM b% points to block:
2270 REM b%!0 : window handle
2280 REM b%!4 : visible area minimum x coordinate
2290 REM b%!8 : visible area minimum y coordinate
2300 REM b%!12 : visible area maximum x coordinate
2310 REM b%!16 : visible area maximum y coordinate
2320 REM b%!20 : scroll x offset relative to work area origin
2330 REM b%!24 : scroll y offset relative to work area origin
2340 REM b%!28 : current graphics window minimum x coordinate
2350 REM b%!32 : current graphics window minimum y coordinate
2360 REM b%!36 : current graphics window maximum x coordinate
2370 REM b%!40 : current graphics window maximum y coordinate
2380 :
2390 REM Fill in as necessary!
2400 ENDPROC
2410 :
2420 DEFFNicon_state(window%,icon%)
2430 REM chacks if icon is selected and returns TRUE or FALSE
2440 LOCAL c%
2450 c%=FNstack(56)
2460 !c%=window%:c%!4=icon%
2470 SYS "Wimp_GetIconState",,c%
2480 PROCunstack(c%)
2490 =((c%!24) AND (1<<21))<>0
2500 :
2510 DEFPROCforce_redraw(window%)
2520 REM forces redraw of the entire screen rectangle covering a window's contents
2530 LOCAL c%
2540 c%=FNstack(36)
2550 !c%=window%
2560 SYS "Wimp_GetWindowState",,c%
2570 SYS "Wimp_ForceRedraw",-1,c%!4,c%!8,c%!12,c%!16
2580 PROCunstack(c%)
2590 ENDPROC
2600 :
2610 DEFFNerror
2620 REM main error handling routine
2630 REM returns TRUE if Cancel box clicked
2640 !b%=ERR
2650 CASE !b% OF
2660 WHEN 1<<30:err_str$="":box%=3
2670 WHEN (1<<30)+1:err_str$="":box%=1
2680 WHEN (1<<30)+2:err_str$="":box%=2
2690 OTHERWISE:err_str$=" at line "+STR$ERL:box%=2
2700 ENDCASE
2710 $(b%+4)=REPORT$+err_str$+CHR$0
2720 SYS "Wimp_ReportError",b%,box%,"Shell" TO ,response%
2730 =(response%=2)
2740 :
2750 DEFPROCterm(a%)
2760 REM replaces terminator on end of string starting at a% with Return character
2770 LOCAL n%
2780 WHILE a%?n%>31
2790 n%+=1
2800 ENDWHILE
2810 a%?n%=13
2820 ENDPROC
2830 :
2840 DEFPROCdrag(window%,icon%)
2850 REM creates drag box to fit icon and starts drag operation
2860 LOCAL c%
2870 c%=FNstack(56)
2880 PROCget_origin(window%,xorig%,yorig%)
2890 !c%=window%:c%!4=icon%
2900 SYS "Wimp_GetIconState",,c%
2910 xmin%=xorig%+c%!8:ymin%=yorig%+c%!12:xmax%=xorig%+c%!16:ymax%=yorig%+c%!20
2920 c%!4=5:REM drag type
2930 c%!8=xmin%:REM coordinates of drag box
2940 c%!12=ymin%
2950 c%!16=xmax%
2960 c%!20=ymax%
2970 c%!24=0:REM screen min x
2980 c%!28=0:REM screen min y
2990 c%!32=1280:REM screen max x
3000 c%!36=1024:REM screen max y
3010 SYS "Wimp_DragBox",,c%
3020 PROCunstack(c%)
3030 ENDPROC
3040 :
3050 DEFFNwind_open(h%)
3060 REM returns TRUE if a window is open
3070 LOCAL c%
3080 c%=FNstack(36)
3090 !c%=h%
3100 SYS "Wimp_GetWindowState",,c%
3110 PROCunstack(c%)
3120 =(c%!32 AND 1<<16)<>0
3130 :
3140 DEFPROCpush(w%,i%)
3150 REM simulates clicking of a push-button icon
3160 LOCAL c%
3170 PROCget_origin(w%,xorig%,yorig%)
3180 c%=FNstack(56)
3190 !c%=w%:c%!4=i%:SYS "Wimp_GetIconState",,c%
3200 x%=xorig%+c%!8:y%=yorig%+c%!12
3210 SYS "OS_ReadMonotonicTime" TO t%
3220 SYS "OS_Byte",138,9,(x%+20) MOD 256
3230 SYS "OS_Byte",138,9,(x%+20) DIV 256
3240 SYS "OS_Byte",138,9,(y%+20) MOD 256
3250 SYS "OS_Byte",138,9,(y%+20) DIV 256
3260 SYS "OS_Byte",138,9,4
3270 SYS "OS_Byte",138,9,t% MOD 256
3280 SYS "OS_Byte",138,9,(t% DIV &100) MOD 256
3290 SYS "OS_Byte",138,9,(t% DIV &10000) MOD 256
3300 SYS "OS_Byte",138,9,(t% DIV &1000000) MOD 256
3310 PROCunstack(c%)
3320 ENDPROC
3330 :
3340 DEFFNtick(menu%,item%)
3350 REM returns TRUE if menu item ticked
3360 =(menu%!(28+item%*24) AND 1)<>0
3370 :
3380 DEFPROCtoggle_tick(menu%,item%)
3390 REM reverses state of tick on menu item
3400 menu%!(28+item%*24)=menu%!(28+item%*24) EOR 1
3410 ENDPROC
3420 :
3430 DEFPROCgrey(menu%,item%)
3440 REM turns menu item grey to disable it
3450 menu%!(28+item%*24+8)=(menu%!(28+item%*24+8)) OR (1<<22)
3460 ENDPROC
3470 :
3480 DEFPROCungrey(menu%,item%)
3490 REM enables menu item and removes greyness
3500 menu%!(28+item%*24+8)=(menu%!(28+item%*24+8)) AND (NOT (1<<22))
3510 ENDPROC
3520 :
3530 DEFFNstring_addr(window%,icon%)
3540 REM returns address of icon's indirected text string
3550 LOCAL c%
3560 c%=FNstack(56)
3570 !c%=window%:c%!4=icon%
3580 SYS "Wimp_GetIconState",,c%
3590 PROCunstack(c%)
3600 =c%!28
3610 :
3620 DEFFNicon_string(window%,icon%)
3630 REM returns icon's indirected text string
3640 PROCterm(FNstring_addr(window%,icon%))
3650 =$FNstring_addr(window%,icon%)
3660 :
3670 DEFPROCset_icon_string(window%,icon%,a$)
3680 REM sets icon's indirected text string
3690 $FNstring_addr(window%,icon%)=a$
3700 ENDPROC
3710 :
3720 DEFFNindirect(a$)
3730 REM puts a$ into indirected icon workspace and returns its address
3740 LOCAL start%
3750 start%=ws%
3760 IF (start%+LENa$+1)>wsend% ERROR (1<<30)+2,"Out of workspace"
3770 $start%=a$
3780 ws%=start%+LENa$+1
3790 =start%
3800 :
3810 DEFPROCupdate_titlebar(window%)
3820 REM redraws title bar of a window;
3830 REM useful when adding or removing " *" to indicate unsaved data
3840 LOCAL c%,tbbottom%
3850 c%=FNstack(36)
3860 !c%=window%:SYS "Wimp_GetWindowState",,c%
3870 tbbottom%=c%!16
3880 SYS "Wimp_GetWindowOutline",,c%
3890 SYS "Wimp_ForceRedraw",-1,c%!4,tbbottom%,c%!12,c%!16
3900 PROCunstack(c%)
3910 ENDPROC
3920 :
Martyn Fox
|